home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / share / hplip / prnt / cups.py < prev    next >
Text File  |  2008-10-13  |  20KB  |  611 lines

  1. # -*- coding: utf-8 -*-
  2. #
  3. # (c) Copyright 2003-2007 Hewlett-Packard Development Company, L.P.
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  18. #
  19. # Author: Don Welch
  20. #
  21.  
  22. # Std Lib
  23. import os
  24. import os.path
  25. import gzip
  26. import re
  27. import time
  28. import urllib
  29. import tempfile
  30. import glob
  31.  
  32. # Local
  33. from base.g import *
  34. from base import utils, models
  35.  
  36. # Handle case where cups.py (via device.py) is loaded
  37. # and cupsext doesn't exist yet. This happens in the
  38. # installer and in a fresh sandbox if the Python extensions
  39. # aren't installed yet.
  40. try:
  41.     import cupsext
  42. except ImportError:
  43.     if not os.getenv("HPLIP_BUILD"):
  44.         log.error("CUPSEXT could not be loaded. Please check HPLIP installation.")
  45.         sys.exit(1)
  46.  
  47. nickname_pat = re.compile(r'''\*NickName:\s*\"(.*)"''', re.MULTILINE)
  48. pat_cups_error_log = re.compile("""^loglevel\s?(debug|debug2|warn|info|error|none)""", re.I)
  49.  
  50. IPP_PRINTER_STATE_IDLE = 3
  51. IPP_PRINTER_STATE_PROCESSING = 4
  52. IPP_PRINTER_STATE_STOPPED = 5
  53.  
  54. PPD_UI_BOOLEAN = 0   # True or False option
  55. PPD_UI_PICKONE = 1   # Pick one from a list
  56. PPD_UI_PICKMANY = 2  # Pick zero or more from a list
  57.  
  58. # Non-std
  59. UI_SPINNER = 100           # Simple spinner with opt. suffix (ie, %)
  60. UI_UNITS_SPINNER = 101     # Spinner control w/pts, cm, in, etc. units
  61. UI_BANNER_JOB_SHEETS = 102 # dual combos for banner job-sheets
  62.  
  63. # ipp_op_t
  64. IPP_PAUSE_PRINTER = 0x0010
  65. IPP_RESUME_PRINTER = 0x011
  66. IPP_PURGE_JOBS = 0x012
  67. CUPS_GET_DEFAULT = 0x4001
  68. CUPS_GET_PRINTERS = 0x4002
  69. CUPS_ADD_MODIFY_PRINTER = 0x4003
  70. CUPS_DELETE_PRINTER = 0x4004
  71. CUPS_GET_CLASSES = 0x4005
  72. CUPS_ADD_MODIFY_CLASS = 0x4006
  73. CUPS_DELETE_CLASS = 0x4007
  74. CUPS_ACCEPT_JOBS = 0x4008
  75. CUPS_REJECT_JOBS = 0x4009
  76. CUPS_SET_DEFAULT = 0x400a
  77. CUPS_GET_DEVICES = 0x400b
  78. CUPS_GET_PPDS = 0x400c
  79. CUPS_MOVE_JOB = 0x400d
  80. CUPS_AUTHENTICATE_JOB = 0x400e
  81.  
  82. # ipp_jstate_t
  83. IPP_JOB_PENDING = 3    # Job is waiting to be printed
  84. IPP_JOB_HELD = 4       # Job is held for printing
  85. IPP_JOB_PROCESSING = 5 # Job is currently printing
  86. IPP_JOB_STOPPED = 6    # Job has been stopped
  87. IPP_JOB_CANCELLED = 7  # Job has been cancelled
  88. IPP_JOB_ABORTED = 8    # Job has aborted due to error
  89. IPP_JOB_COMPLETED = 8  # Job has completed successfully
  90.  
  91. # ipp_status_e
  92. IPP_OK = 0x0000 # successful-ok 
  93. IPP_OK_SUBST = 0x001 # successful-ok-ignored-or-substituted-attributes 
  94. IPP_OK_CONFLICT = 0x002 # successful-ok-conflicting-attributes 
  95. IPP_OK_IGNORED_SUBSCRIPTIONS = 0x003 # successful-ok-ignored-subscriptions 
  96. IPP_OK_IGNORED_NOTIFICATIONS = 0x004 # successful-ok-ignored-notifications 
  97. IPP_OK_TOO_MANY_EVENTS = 0x005 # successful-ok-too-many-events 
  98. IPP_OK_BUT_CANCEL_SUBSCRIPTION = 0x006 # successful-ok-but-cancel-subscription 
  99. IPP_OK_EVENTS_COMPLETE = 0x007 # successful-ok-events-complete 
  100. IPP_REDIRECTION_OTHER_SITE = 0x300
  101. IPP_BAD_REQUEST = 0x0400 # client-error-bad-request 
  102. IPP_FORBIDDEN = 0x0401 # client-error-forbidden 
  103. IPP_NOT_AUTHENTICATED = 0x0402 # client-error-not-authenticated 
  104. IPP_NOT_AUTHORIZED = 0x0403 # client-error-not-authorized 
  105. IPP_NOT_POSSIBLE = 0x0404 # client-error-not-possible 
  106. IPP_TIMEOUT = 0x0405 # client-error-timeout 
  107. IPP_NOT_FOUND = 0x0406 # client-error-not-found 
  108. IPP_GONE = 0x0407 # client-error-gone 
  109. IPP_REQUEST_ENTITY = 0x0408 # client-error-request-entity-too-large 
  110. IPP_REQUEST_VALUE = 0x0409 # client-error-request-value-too-long 
  111. IPP_DOCUMENT_FORMAT = 0x040a # client-error-document-format-not-supported 
  112. IPP_ATTRIBUTES = 0x040b # client-error-attributes-or-values-not-supported 
  113. IPP_URI_SCHEME = 0x040c # client-error-uri-scheme-not-supported 
  114. IPP_CHARSET = 0x040d # client-error-charset-not-supported 
  115. IPP_CONFLICT = 0x040e # client-error-conflicting-attributes 
  116. IPP_COMPRESSION_NOT_SUPPORTED = 0x040f # client-error-compression-not-supported 
  117. IPP_COMPRESSION_ERROR = 0x0410 # client-error-compression-error 
  118. IPP_DOCUMENT_FORMAT_ERROR = 0x0411 # client-error-document-format-error 
  119. IPP_DOCUMENT_ACCESS_ERROR = 0x0412 # client-error-document-access-error 
  120. IPP_ATTRIBUTES_NOT_SETTABLE = 0x0413 # client-error-attributes-not-settable 
  121. IPP_IGNORED_ALL_SUBSCRIPTIONS = 0x0414 # client-error-ignored-all-subscriptions 
  122. IPP_TOO_MANY_SUBSCRIPTIONS = 0x0415 # client-error-too-many-subscriptions 
  123. IPP_IGNORED_ALL_NOTIFICATIONS = 0x0416 # client-error-ignored-all-notifications 
  124. IPP_PRINT_SUPPORT_FILE_NOT_FOUND = 0x0417 # client-error-print-support-file-not-found 
  125. IPP_INTERNAL_ERROR = 0x0500 # server-error-internal-error 
  126. IPP_OPERATION_NOT_SUPPORTED = 0x0501 # server-error-operation-not-supported 
  127. IPP_SERVICE_UNAVAILABLE = 0x0502 # server-error-service-unavailable 
  128. IPP_VERSION_NOT_SUPPORTED = 0x0503 # server-error-version-not-supported 
  129. IPP_DEVICE_ERROR = 0x0504 # server-error-device-error 
  130. IPP_TEMPORARY_ERROR = 0x0505 # server-error-temporary-error 
  131. IPP_NOT_ACCEPTING = 0x0506 # server-error-not-accepting-jobs 
  132. IPP_PRINTER_BUSY = 0x0507 # server-error-busy 
  133. IPP_ERROR_JOB_CANCELLED = 0x0508 # server-error-job-canceled 
  134. IPP_MULTIPLE_JOBS_NOT_SUPPORTED = 0x0509 # server-error-multiple-document-jobs-not-supported 
  135. IPP_PRINTER_IS_DEACTIVATED = 0x050a # server-error-printer-is-deactivated 
  136.  
  137. CUPS_ERROR_BAD_NAME = 0x0f00
  138. CUPS_ERROR_BAD_PARAMETERS = 0x0f01
  139.  
  140.  
  141.  
  142. ##def restartCUPS(): # must be root. How do you check for this?
  143. ##    os.system('killall -HUP cupsd')
  144.  
  145. def getPPDPath(addtional_paths=None):
  146.     """
  147.         Returns the CUPS ppd path (not the foomatic one under /usr/share/ppd).
  148.         Usually this is /usr/share/cups/model.
  149.     """
  150.     if addtional_paths is None:
  151.         addtional_paths = []
  152.  
  153.     search_paths = prop.ppd_search_path.split(';') + addtional_paths
  154.  
  155.     for path in search_paths:
  156.         ppd_path = os.path.join(path, 'cups/model')
  157.         if os.path.exists(ppd_path):
  158.             return ppd_path
  159.  
  160.  
  161. def getAllowableMIMETypes():
  162.     """
  163.         Scan all /etc/cups/*.convs files for allowable file formats.
  164.     """
  165.     files = glob.glob("/etc/cups/*.convs")
  166.  
  167.     allowable_mime_types = []
  168.  
  169.     for f in files:
  170.         #log.debug( "Capturing allowable MIME types from: %s" % f )
  171.         conv_file = file(f, 'r')
  172.  
  173.         for line in conv_file:
  174.             if not line.startswith("#") and len(line) > 1:
  175.                 try:
  176.                     source, dest, cost, prog =  line.split()
  177.                 except ValueError:
  178.                     continue
  179.  
  180.                 if source not in ('application/octet-stream', 'application/vnd.cups-postscript'):
  181.                     allowable_mime_types.append(source)
  182.  
  183.     # Add some well-known MIME types that may not appear in the .convs files
  184.     allowable_mime_types.append("image/x-bmp")
  185.     allowable_mime_types.append("text/cpp")
  186.  
  187.     return allowable_mime_types
  188.  
  189. def getPPDDescription(f):
  190.     if f.endswith('.gz'):
  191.         nickname = gzip.GzipFile(f, 'r').read(4096)
  192.     else:
  193.         nickname = file(f, 'r').read(4096)
  194.  
  195.     try:
  196.         desc = nickname_pat.search(nickname).group(1)
  197.     except AttributeError:
  198.         desc = ''
  199.  
  200.     return desc
  201.  
  202.  
  203. def getSystemPPDs():
  204.     major, minor, patch = getVersionTuple()
  205.     ppds = {} # {'ppd name' : 'desc', ...}
  206.  
  207.     if major == 1 and minor < 2:
  208.         log.debug("(CUPS 1.1.x) Searching for PPDs in: %s" % sys_cfg.dirs.ppd)
  209.  
  210.         for f in utils.walkFiles(sys_cfg.dirs.ppd, pattern="HP*ppd*;hp*ppd*", abs_paths=True):
  211.             desc = getPPDDescription(f)
  212.  
  213.             if not ('foo2' in desc or 
  214.                     'gutenprint' in desc.lower() or 
  215.                     'gutenprint' in f):
  216.  
  217.                 ppds[f] = desc
  218.                 log.debug("%s: %s" % (f, desc))
  219.  
  220.     else: # 1.2.x
  221.         log.debug("(CUPS 1.2.x) Getting list of PPDs using CUPS_GET_PPDS...")
  222.         ppd_dict = cupsext.getPPDList()
  223.         cups_ppd_path = getPPDPath() # usually /usr/share/cups/model
  224.         foomatic_ppd_path = sys_cfg.dirs.ppdbase # usually /usr/share/ppd
  225.  
  226.         if not foomatic_ppd_path or not os.path.exists(foomatic_ppd_path):
  227.             foomatic_ppd_path = '/usr/share/ppd'
  228.  
  229.         log.debug("CUPS PPD base path = %s" % cups_ppd_path)
  230.         log.debug("Foomatic PPD base path = %s" % foomatic_ppd_path)
  231.  
  232.         for ppd in ppd_dict:
  233.             if not ppd:
  234.                 continue
  235.  
  236.             if 'hp-' in ppd.lower() or 'hp_' in ppd.lower() and \
  237.                 ppd_dict[ppd]['ppd-make'] == 'HP':
  238.  
  239.                 desc = ppd_dict[ppd]['ppd-make-and-model']
  240.                 #print ppd, desc
  241.  
  242.                 if not ('foo2' in desc.lower() or 
  243.                         'gutenprint' in desc.lower() or 
  244.                         'gutenprint' in ppd):
  245.  
  246.                     # PPD files returned by CUPS_GET_PPDS (and by lpinfo -m)
  247.                     # can be relative to /usr/share/ppd/ or to 
  248.                     # /usr/share/cups/model/. Not sure why this is.
  249.                     # Here we will try both and see which one it is...
  250.                     
  251.                     if os.path.exists(ppd):
  252.                         path = ppd
  253.                     else:
  254.                         try:
  255.                             path = os.path.join(foomatic_ppd_path, ppd)
  256.                         except AttributeError: # happens on some boxes with provider: style ppds (foomatic: etc)
  257.                             path = ppd
  258.                         else:
  259.                             if not os.path.exists(path):
  260.                                 try:
  261.                                     path = os.path.join(cups_ppd_path, ppd)
  262.                                 except AttributeError:
  263.                                     path = ppd
  264.                                 else:
  265.                                     if not os.path.exists(path):
  266.                                         path = ppd # foomatic: or some other driver
  267.  
  268.                     ppds[path] = desc
  269.                     log.debug("%s: %s" % (path, desc))
  270.  
  271.     return ppds
  272.  
  273.  
  274. # TODO: Move this to CUPSEXT for better performance
  275. def levenshtein_distance(a,b):
  276.     """
  277.     Calculates the Levenshtein distance between a and b.
  278.     Written by Magnus Lie Hetland.
  279.     """
  280.     n, m = len(a), len(b)
  281.     if n > m:
  282.         a,b = b,a
  283.         n,m = m,n
  284.  
  285.     current = range(n+1)
  286.     for i in range(1,m+1):
  287.         previous, current = current, [i]+[0]*m
  288.  
  289.         for j in range(1,n+1):
  290.             add, delete = previous[j]+1, current[j-1]+1
  291.             change = previous[j-1]
  292.  
  293.             if a[j-1] != b[i-1]:
  294.                 change = change + 1
  295.  
  296.             current[j] = min(add, delete, change)
  297.  
  298.     return current[n]
  299.  
  300.  
  301. number_pat = re.compile(r""".*?(\d+)""", re.IGNORECASE)
  302.  
  303. STRIP_STRINGS = ['foomatic:', 'hp-', 'hp_', 'hp ', '_series', '.gz', '.ppd', '-series', ' series', '-hpijs']
  304.  
  305. for p in models.TECH_CLASS_PDLS.values():
  306.     pp = '-%s' % p
  307.     if pp not in STRIP_STRINGS:
  308.         STRIP_STRINGS.append(pp)
  309.         
  310.  
  311. def stripModel(model):
  312.     model = model.lower()
  313.  
  314.     for x in STRIP_STRINGS:
  315.         model = model.replace(x, '')
  316.  
  317.     return model
  318.  
  319.  
  320.  
  321. def getPPDFile(stripped_model, ppds):
  322.     """
  323.         Match up a model name to a PPD from a list of system PPD files.
  324.     """
  325.     log.debug("1st stage edit distance match")
  326.     mins = {}
  327.     eds = {}
  328.     min_edit_distance = sys.maxint
  329.  
  330.     log.debug("Determining edit distance from %s..." % stripped_model)
  331.     for f in ppds:
  332.         t = stripModel(os.path.basename(f))
  333.         eds[f] = levenshtein_distance(stripped_model, t)
  334.         log.debug("dist('%s') = %d" % (t, eds[f]))
  335.         min_edit_distance = min(min_edit_distance, eds[f])
  336.  
  337.     log.debug("Min. dist = %d" % min_edit_distance)
  338.  
  339.     for f in ppds:
  340.         if eds[f] == min_edit_distance:
  341.             for m in mins:
  342.                 if os.path.basename(m) == os.path.basename(f):
  343.                     break # File already in list possibly with different path (Ubuntu, etc)
  344.             else:
  345.                 mins[f] = ppds[f]
  346.  
  347.     log.debug(mins)
  348.  
  349.     if len(mins) > 1: # try pattern matching the model number
  350.         log.debug("2nd stage matching with model number")
  351.  
  352.         try:
  353.             model_number = number_pat.match(stripped_model).group(1)
  354.             model_number = int(model_number)
  355.         except AttributeError:
  356.             pass
  357.         except ValueError:
  358.             pass
  359.         else:
  360.             log.debug("model_number=%d" % model_number)
  361.             matches = {} #[]
  362.             for x in range(3): # 1, 10, 100
  363.                 factor = 10**x
  364.                 log.debug("Factor = %d" % factor)
  365.                 adj_model_number = int(model_number/factor)*factor
  366.                 number_matching, match = 0, ''
  367.  
  368.                 for m in mins:
  369.                     try:
  370.                         mins_model_number = number_pat.match(os.path.basename(m)).group(1)
  371.                         mins_model_number = int(mins_model_number)
  372.                         log.debug("mins_model_number= %d" % mins_model_number)
  373.                     except AttributeError:
  374.                         continue
  375.                     except ValueError:
  376.                         continue
  377.  
  378.                     mins_adj_model_number = int(mins_model_number/factor)*factor
  379.                     log.debug("mins_adj_model_number=%d" % mins_adj_model_number)
  380.                     log.debug("adj_model_number=%d" % adj_model_number)
  381.  
  382.                     if mins_adj_model_number == adj_model_number:
  383.                         log.debug("match")
  384.                         number_matching += 1
  385.                         matches[m] = ppds[m]
  386.                         log.debug(matches)
  387.  
  388.                     log.debug("***")
  389.  
  390.                 if len(matches):
  391.                     mins = matches
  392.                     break
  393.  
  394.     return mins
  395.  
  396.  
  397. def getErrorLogLevel():
  398.     cups_conf = '/etc/cups/cupsd.conf'
  399.     try:
  400.         f = file(cups_conf, 'r')
  401.     except OSError:
  402.         log.error("%s not found." % cups_conf)
  403.     except IOError:
  404.         log.error("%s: I/O error." % cups_conf)
  405.     else:
  406.         for l in f:
  407.             m = pat_cups_error_log.match(l)
  408.             if m is not None:
  409.                 level = m.group(1).lower()
  410.                 log.debug("CUPS error_log LogLevel: %s" % level)
  411.                 return level
  412.  
  413.     log.debug("CUPS error_log LogLevel: unknown")
  414.     return 'unknown'
  415.  
  416.  
  417. def getPrintJobErrorLog(job_id, max_lines=1000, cont_interval=5):
  418.     ret = []
  419.     s = '[Job %d]' % job_id
  420.     #level = getErrorLogLevel()
  421.     cups_conf = '/var/log/cups/error_log'
  422.  
  423.     #if level in ('debug', 'debug2'):
  424.     if 1:
  425.         try:
  426.             f = file(cups_conf, 'r')
  427.         except (IOError, OSError):
  428.             log.error("Could not open the CUPS error_log file: %s" % cups_conf)
  429.             return ''
  430.  
  431.         else:
  432.             if s in file(cups_conf, 'r').read():
  433.                 queue = utils.Queue()
  434.                 job_found = False
  435.  
  436.                 while True:
  437.                     line = f.readline()
  438.  
  439.                     if s in line:
  440.                         job_found = True
  441.  
  442.                         while len(queue):
  443.                             ret.append(queue.get())
  444.  
  445.                         ret.append(line.strip())
  446.  
  447.                         if len(ret) > max_lines:
  448.                             break
  449.  
  450.                     else:
  451.                         if job_found:
  452.                             queue.put(line.strip())
  453.  
  454.                             if len(queue) > cont_interval:
  455.                                 break
  456.  
  457.             return '\n'.join(ret)
  458.  
  459.  
  460. #
  461. # cupsext wrappers
  462. #
  463.  
  464. def getDefaultPrinter():
  465.     r = cupsext.getDefaultPrinter()
  466.     if r is None:
  467.         log.debug("The CUPS default printer is not set.")
  468.     return r
  469.  
  470. def setDefaultPrinter(printer_name):
  471.     return cupsext.setDefaultPrinter(printer_name)
  472.  
  473. def accept(printer_name):
  474.     return controlPrinter(printer_name, CUPS_ACCEPT_JOBS)
  475.  
  476. def reject(printer_name):
  477.     return controlPrinter(printer_name, CUPS_REJECT_JOBS)
  478.  
  479. def start(printer_name):
  480.     return controlPrinter(printer_name, IPP_RESUME_PRINTER)
  481.  
  482. def stop(printer_name):
  483.     return controlPrinter(printer_name, IPP_PAUSE_PRINTER)
  484.  
  485. def purge(printer_name):
  486.     return controlPrinter(printer_name, IPP_PURGE_JOBS)
  487.  
  488. def controlPrinter(printer_name, cups_op):
  489.     if cups_op in (CUPS_ACCEPT_JOBS, CUPS_REJECT_JOBS, IPP_PAUSE_PRINTER, IPP_RESUME_PRINTER, IPP_PURGE_JOBS):
  490.         return cupsext.controlPrinter(printer_name, cups_op)
  491.  
  492.     return 0;
  493.  
  494. def openPPD(printer):
  495.     if not printer:
  496.         return
  497.  
  498.     return cupsext.openPPD(printer)
  499.  
  500. def closePPD():
  501.     return cupsext.closePPD()
  502.  
  503. def getPPD(printer):
  504.     if not printer:
  505.         return
  506.  
  507.     return cupsext.getPPD(printer)
  508.  
  509. def getPPDOption(option):
  510.     return cupsext.getPPDOption(option)
  511.  
  512. def getPPDPageSize():
  513.     return cupsext.getPPDPageSize()
  514.  
  515. def getPrinters():
  516. ##    p2 = []
  517. ##    p = cupsext.getPrinters()
  518. ##    for pp in p:
  519. ##        print pp
  520. ##        try:
  521. ##            pn = pp.name.decode('utf-8')
  522. ##        except UnicodeError:
  523. ##            pass
  524. ##            
  525. ##        p2.append(pp)
  526. ##        
  527. ##    return p2
  528.     return cupsext.getPrinters()
  529.  
  530. def getJobs(my_job=0, completed=0):
  531.     return cupsext.getJobs(my_job, completed)
  532.  
  533. def getAllJobs(my_job=0):
  534.     return cupsext.getJobs(my_job, 0) + cupsext.getJobs(my_job, 1)
  535.  
  536. def getVersion():
  537.     return cupsext.getVersion()
  538.  
  539. def getVersionTuple():
  540.     return cupsext.getVersionTuple()
  541.  
  542. def getServer():
  543.     return cupsext.getServer()
  544.  
  545. def cancelJob(jobid, dest=None):
  546.     if dest is not None:
  547.         return cupsext.cancelJob(dest, jobid)
  548.     else:
  549.         jobs = cupsext.getJobs(0, 0)
  550.         for j in jobs:
  551.             if j.id == jobid:
  552.                 return cupsext.cancelJob(j.dest, jobid)
  553.  
  554.     return False
  555.  
  556. def resetOptions():
  557.     return cupsext.resetOptions()
  558.  
  559. def addOption(option):
  560.     return cupsext.addOption(option)
  561.  
  562. def getOptions():
  563.     return cupsext.getOptions()
  564.  
  565. def printFile(printer, filename, title):
  566.     if os.path.exists(filename):
  567.         return cupsext.printFileWithOptions(printer, filename, title)
  568.     else:
  569.         return -1
  570.  
  571. def addPrinter(printer_name, device_uri, location, ppd_file, model, info):
  572.     log.debug("addPrinter('%s', '%s', '%s', '%s', '%s', '%s')" %
  573.         ( printer_name, device_uri, location, ppd_file, model, info))
  574.  
  575.     if ppd_file and not os.path.exists(ppd_file):
  576.         log.error("PPD file '%s' not found." % ppd_file)
  577.         return (-1, "PPD file not found")
  578.  
  579.     return cupsext.addPrinter(printer_name, device_uri, location, ppd_file, model, info)
  580.  
  581. def delPrinter(printer_name):
  582.     return cupsext.delPrinter(printer_name)
  583.  
  584. def getGroupList():
  585.     return cupsext.getGroupList()
  586.  
  587. def getGroup(group):
  588.     return cupsext.getGroup(group)
  589.  
  590. def getOptionList(group):
  591.     return cupsext.getOptionList(group)
  592.  
  593. def getOption(group, option):
  594.     return cupsext.getOption(group, option)
  595.  
  596. def getChoiceList(group, option):
  597.     return cupsext.getChoiceList(group, option)
  598.  
  599. def getChoice(group, option, choice):
  600.     return cupsext.getChoice(group, option, choice)
  601.  
  602. def setOptions():
  603.     return cupsext.setOptions()
  604.  
  605. def removeOption(option):
  606.     return cupsext.removeOption(option)
  607.  
  608. def setPasswordCallback(func):
  609.     return cupsext.setPasswordCallback(func)
  610.  
  611.